import java.io.*;
import java.util.*;
/**
 * Converts a source program into a syntax highlighted
 * representation for LaTeX.
 * 
 * @author James Stanier
 */
public class CodeIndexGenerator {
	private String fileName;
	private String program;
	private char current;
	boolean carat;
	private ArrayList<String> keywords;
	private ArrayList<String> variables;
	private BufferedReader br;
	private Stack<String> scopeStack;
	private ArrayList<String> outputArray;
	
	/**
	 * Constructor.
	 * @param f File name of source program.
	 * @param v Symbol table variables from that source.
	 */
	public CodeIndexGenerator(String f, ArrayList<String> v) {
		try {
			outputArray = new ArrayList<String>();
			scopeStack = new Stack<String>();
			variables = v;
			
			// Required for trimming enum name
			for(int i = 0; i < variables.size(); i++) {
				String temp = variables.get(i);
				if(temp.contains(" ")) {
					String[] splitArray = temp.split(" ");
					variables.remove(i);
					variables.add(splitArray[0]);
				}
			}
			
			fileName = f;
			
			keywords = new ArrayList<String>();
			keywords.add("package");
			keywords.add("import");
			keywords.add("byte");
			keywords.add("char");
			keywords.add("short");
			keywords.add("int");
			keywords.add("long");
			keywords.add("float");
			keywords.add("double");
			keywords.add("boolean");
			keywords.add("void");
			keywords.add("class");
			keywords.add("interface");
			keywords.add("abstract");
			keywords.add("final");
			keywords.add("private");
			keywords.add("protected");
			keywords.add("public");
			keywords.add("static");
			keywords.add("synchronized");
			keywords.add("native");
			keywords.add("volatile");
			keywords.add("transient");
			keywords.add("break");
			keywords.add("case");
			keywords.add("continue");
			keywords.add("default");
			keywords.add("do");
			keywords.add("else");
			keywords.add("for");
			keywords.add("if");
			keywords.add("instanceof");
			keywords.add("new");
			keywords.add("return");
			keywords.add("switch");
			keywords.add("while");
			keywords.add("throw");
			keywords.add("try");
			keywords.add("catch");
			keywords.add("extends");
			keywords.add("finally");
			keywords.add("implements");
			keywords.add("throws");
			keywords.add("this");
			keywords.add("null");
			keywords.add("super");
			keywords.add("true");
			keywords.add("false");
			keywords.add("enum");
		}
		catch (Exception e) {
			System.err.println(e);
			e.printStackTrace();
		}
	}
	
	public ArrayList<String> generateIndex() {
		boolean literal = false;
		char lastBrace = 'n';
		try {
			br = new BufferedReader(new FileReader(fileName));
			current = (char)br.read();
		}
		catch (Exception e) {
			System.err.println(e);
			e.printStackTrace();
		}
		
		String buffer = "";
		
		// Trim name from absolute path
		String longFileName = JavaParser.getFileName();
		int counter = 0;
		char c = System.getProperty("file.separator").toCharArray()[0];
		for(int j = longFileName.length()-1; j >=0; j--) {
			counter = j;
			if(longFileName.charAt(j) == c) {
				break;
			}
		}
		
		longFileName = longFileName.substring(counter+1);
		
		outputArray.add("\n\\newpage\n\\section{Code index for \\textsf{" + longFileName + "}}\n");
		outputArray.add("\\definecolor{blue}{rgb}{0, 0.3, 0.5}\n");
		outputArray.add("\\definecolor{red}{rgb}{0.75, 0.25, 0.5}\n");
		outputArray.add("\\setlength{\\parskip}{3pt}\\small\n");
		
		while(current != 65535) {
			if(current == '/') {
				getNextChar();
				if(current == '*') {
					while(current == '*') {
						getNextChar();
					}
					while(current != '*') {
						getNextChar();
						if(current == '*') {
							getNextChar();
							if(current == '/') {
								break;
							}
						}
					}
					
				}
				else if(current == '/') {
					while(current != '\n') {
						getNextChar();
					}
					outputArray.add("\\par\n");
					if(scopeStack.size() > 0) {
						for(int i = 0; i < scopeStack.size()+1; i++) {
							outputArray.add("\\hspace*{10pt}");
						}
						while(current == '\u0009') {
							getNextChar();
						}
					}
				}
				while(Character.isWhitespace(current)) {
					getNextChar();
				}
				
			}
			else if(Character.isWhitespace(current)) {
				if(current == '\u0009') {
					if(scopeStack.size() > 0) {
						for(int i = 0; i < scopeStack.size()+1; i++) {
							outputArray.add("\\hspace*{10pt}");
						}
						while(current == '\u0009') {
							getNextChar();
						}
					}
				}
				else if(current == '\n') {
					outputArray.add("\\par\n");
					getNextChar();
				}
				else {
					outputArray.add(Character.toString(current));
					getNextChar();
				}
			}
			else {
				String saved = "";
				while(!Character.isWhitespace(current) && current != 65535) {
					
					if(current == ';' || current == '(' || current == ')' || current == '+' || current == '-'
						|| current == '/' || current == '*' || current == '.' || current == ':' ||
						current == '=' || current == '.' || current == ',') {
						saved += current;
						getNextChar();
						break;
					}
					if(current =='{') {
						lastBrace = '{';
						scopeStack.push("{");
					}
					else if(current == '}') {
						lastBrace = '}';
						scopeStack.pop();

						if(outputArray.get(outputArray.size()-1).equals("\\hspace*{10pt}")) {
							outputArray.remove(outputArray.size()-1);
						}
					}
					if(current == '&' || current == '_' || current == '%') {
						buffer += "\\" + current;
						getNextChar();
					}
					else if(current == '^') {
						buffer += current;
						carat = true;
						getNextChar();
					}
					else if(current == '\\') {
						buffer += "$\\backslash$";
						getNextChar();
					}
					// Char or string literals
					else if(current == '\'' || current == '"') {
						char lastChar = 'a';
						int backslashCount = 0;
						boolean str;
						if(current == '\'') {
							str = false;
						}
						else {
							str = true;
						}
						literal = true;
						if(str == true) {
							buffer += "{\\color{blue}" + "\"";
							getNextChar();
							while(true) {
								if(current == '&' || current == '{' || current == '}'
									|| current == '$' || current == '%' || current == '_') {
									lastChar = current;
									buffer += "\\" + current;
									getNextChar();
								}
								else if(current == '^') {
									carat = true;
									lastChar = current;
									buffer += current;
									getNextChar();
								}
								else if(current == '\\') {
									backslashCount++;
									lastChar = current;
									buffer += "$\\backslash$";
									getNextChar();
								}
								else if(current == '"') {
									if(lastChar == '\\' && backslashCount % 2 == 0) {
										break;
									}
									else if(lastChar == '\\' && backslashCount % 2 == 1) {
										lastChar = current;
										buffer += current;
										getNextChar();
									}
									else {
										break;
									}
								}
								else {
									lastChar = current;
									buffer += current;
									getNextChar();
								}
							}
						}
						else {
							backslashCount = 0;
							lastChar = current;
							buffer += "{\\color{blue}" + "`";
							getNextChar();
							while(true) {
								if(current == '&' || current == '{' || current == '}' ||
									current == '$' || current == '%' || current == '_') {
									lastChar = current;
									buffer += "\\" + current;
									getNextChar();
								}
								else if(current == '^') {
									carat = true;
									lastChar = current;
									buffer += current;
									getNextChar();
								}
								else if(current == '\\') {
									backslashCount++;
									lastChar = current;
									buffer += "$\\backslash$";
									getNextChar();
								}
								else if(current == '\'') {
									if(lastChar == '\\' && backslashCount % 2 == 0) {
										break;
									}
									else if(lastChar == '\\' && backslashCount % 2 == 1) {
										lastChar = current;
										buffer += current;
										getNextChar();
									}
									else {
										break;
									}
								}
								else {
									lastChar = current;
									buffer += current;
									getNextChar();
								}
							}
						}
						
						buffer += current + "}";
						getNextChar();
						
					}
					
					else {
						buffer += current;
						getNextChar();
					}
				}
				
				if(keywords.contains(buffer)) {
					String temp = "";
					temp = buffer.replace("{", "\\{");
					outputArray.add("{\\color{red}\\texttt{" + temp.replace("}", "\\}") + "}}" + 
							"\\texttt{" + saved + "}");
				}
				else if(variables.contains(buffer)) {
					String temp = "";
					temp = buffer.replace("{", "\\{");
					outputArray.add("{\\color{blue}\\texttt{" + temp.replace("}", "\\}") + "}}" + 
							"\\texttt{" + saved + "}");
				}
				else {
					if(literal == false) {
						String temp = "";
						temp = buffer.replace("{", "\\{");
						temp = temp.replace("}", "\\}");
						if(carat) {
							temp = temp.replace("^", "\\verb1^1");
							outputArray.add("\\tt " + temp + saved + "\\rm");
						}
						else {
							outputArray.add("\\texttt{" + temp + saved + "}");
						}
					}
					else {
						literal = false;
						if(carat) {
							String temp = buffer.replace("^", "\\verb1^1");
							temp.replace("{\\color{", "\\color{");
							outputArray.add("\\tt " + temp + saved + "\\rm");
						}
						else {
							outputArray.add("\\texttt{" + buffer + saved + "}");
						}
					}
				}
				buffer = "";
				carat = false;
			}
		}
		
		outputArray.add("\n");
		try {
			br.close();
		}
		catch (Exception e) {
			System.err.println(e);
			e.printStackTrace();
		}
		return outputArray;
	}
	
	/**
	 * Reads the next character from file.
	 */
	public void getNextChar() {
		try {
			current = (char)br.read();
		}
		catch (Exception e) {
			System.err.println(e);
			e.printStackTrace();
		}
	}
}
